﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;
using CsBinder = Microsoft.CSharp.RuntimeBinder.Binder;

using King.Reflection;
using King.Extensions;
using King.Collections;

namespace King.Reflection {

    public class Dynamic {

        public static Func<object, object[], object> Bind(
            string name, 
            Type sourceType, 
            IEnumerable<Type> argumentTypes = null) {

            IEnumerable<Type> typeArguments = null;

            // default context
            var context = sourceType;

            // delegate signature
            var signature = new List<Type> { typeof(CallSite), typeof(object) };
            signature.AddRange(argumentTypes);
            signature.Add(typeof(object));

            // delegate Func<CallSite, source, argument0, ..., argumentN>
            var delegateTypeName = "System.Func`" + signature.Count;
            if (signature.Count > 9)
                delegateTypeName += "," + typeof(Func<,,,,,,,,,>).Assembly.FullName;
            var delegateType = Type.GetType(delegateTypeName).MakeGenericType(signature.ToArray());

            // binder
            var binder = CsBinder.InvokeMember(
                CSharpBinderFlags.None, name, typeArguments, context,
                Enumerable.Repeat(0, argumentTypes.Count() + 1)
                    .Select(o => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null))
                    .ToArray()
            );

            // callsite
            var callSite = CallSite.Create(delegateType, binder);

            // CallSite<Func<CallSite, source, argument0, ..., argumentN>>.Target
            var bf = BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance;
            var target = (Delegate)callSite.GetType().InvokeMember("Target", bf, Type.DefaultBinder, callSite, null);

            // close over callSite
            var result = (Func<object, object[], object>)
                delegate(object source, object[] arguments) {
                    // invoke
                    var delegateArguments = new List<object> { callSite, source };
                    delegateArguments.AddRange(arguments);
                    return target.DynamicInvoke(delegateArguments.ToArray());
                };

            // return result
            return result;
        }
    }
}